package com.zeyu.framework.modules.common.openid.provider;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.brickred.socialauth.AbstractProvider;
import org.brickred.socialauth.AuthProvider;
import org.brickred.socialauth.Contact;
import org.brickred.socialauth.Permission;
import org.brickred.socialauth.Profile;
import org.brickred.socialauth.exception.AccessTokenExpireException;
import org.brickred.socialauth.exception.ServerDataException;
import org.brickred.socialauth.exception.SocialAuthException;
import org.brickred.socialauth.exception.UserDeniedPermissionException;
import org.brickred.socialauth.oauthstrategy.OAuth2;
import org.brickred.socialauth.oauthstrategy.OAuthStrategyBase;
import org.brickred.socialauth.util.AccessGrant;
import org.brickred.socialauth.util.Constants;
import org.brickred.socialauth.util.MethodType;
import org.brickred.socialauth.util.OAuthConfig;
import org.brickred.socialauth.util.Response;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * GitHub第三方登录服务提供者
 * Created by zeyuphoenix on 2017/1/4.
 */
public class GithubAuthProvider extends AbstractProvider implements AuthProvider, Serializable {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * uid
     */
    private static final long serialVersionUID = 1L;
    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(GithubAuthProvider.class);

    // auth url
    private static final String PROFILE_URL = "https://api.github.com/user";
    private static final String EMAILS_URL = "https://api.github.com/user/emails";

    // perms
    private static final String[] AllPerms = new String[]{"user",
            "user:email"};
    private static final String[] AuthPerms = new String[]{"user",
            "user:email"};

    // end ponit
    private static final Map<String, String> ENDPOINTS;

    static {
        ENDPOINTS = Maps.newHashMap();
        ENDPOINTS.put(Constants.OAUTH_AUTHORIZATION_URL,
                "https://github.com/login/oauth/authorize");
        ENDPOINTS.put(Constants.OAUTH_ACCESS_TOKEN_URL,
                "https://github.com/login/oauth/access_token");
    }

    // ================================================================
    // Fields
    // ================================================================

    // parm
    private Permission scope;
    private OAuthConfig config;
    private Profile userProfile;
    private AccessGrant accessGrant;
    private OAuthStrategyBase authenticationStrategy;

    // ================================================================
    // Constructors
    // ================================================================

    /**
     * Stores configuration for the provider
     *
     * @param providerConfig
     *            It contains the configuration of application like consumer key
     *            and consumer secret
     * @throws Exception
     */
    public GithubAuthProvider(final OAuthConfig providerConfig) throws Exception {
        config = providerConfig;
        if (config.getCustomPermissions() != null) {
            scope = Permission.CUSTOM;
        }

        if (config.getAuthenticationUrl() != null) {
            ENDPOINTS.put(Constants.OAUTH_AUTHORIZATION_URL,
                    config.getAuthenticationUrl());
        } else {
            config.setAuthenticationUrl(ENDPOINTS
                    .get(Constants.OAUTH_AUTHORIZATION_URL));
        }

        if (config.getAccessTokenUrl() != null) {
            ENDPOINTS.put(Constants.OAUTH_ACCESS_TOKEN_URL,
                    config.getAccessTokenUrl());
        } else {
            config.setAccessTokenUrl(ENDPOINTS
                    .get(Constants.OAUTH_ACCESS_TOKEN_URL));
        }

        authenticationStrategy = new OAuth2(config, ENDPOINTS);
        authenticationStrategy.setPermission(scope);
        authenticationStrategy.setScope(getScope());
    }

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    @Override
    public String getLoginRedirectURL(final String successUrl) throws Exception {
        return authenticationStrategy.getLoginRedirectURL(successUrl);
    }

    @Override
    public Profile verifyResponse(final Map<String, String> requestParams)
            throws Exception {
        return doVerifyResponse(requestParams);
    }

    @Override
    public Response updateStatus(final String msg) throws Exception {
        logger.warn("WARNING: Not implemented for GitHub");
        throw new SocialAuthException(
                "Update Status is not implemented for GitHub");
    }

    @Override
    public List<Contact> getContactList() throws Exception {
        logger.warn("WARNING: Not implemented for GitHub");
        throw new SocialAuthException(
                "Get contact list is not implemented for GitHub");
    }

    @Override
    public Profile getUserProfile() throws Exception {
        if (userProfile == null && accessGrant != null) {
            getProfile();
        }
        return userProfile;
    }

    @Override
    public void logout() {
        accessGrant = null;
        authenticationStrategy.logout();
    }

    @Override
    public void setPermission(final Permission p) {
        logger.debug("Permission requested : " + p.toString());
        this.scope = p;
        authenticationStrategy.setPermission(this.scope);
        authenticationStrategy.setScope(getScope());
    }

    @Override
    public Response api(final String url, final String methodType,
                        final Map<String, String> params,
                        final Map<String, String> headerParams, final String body)
            throws Exception {
        logger.info("Calling api function for url	:	" + url);
        Response response;
        try {
            response = authenticationStrategy.executeFeed(url, methodType,
                    params, headerParams, body);
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Error while making request to URL : " + url, e);
        }
        if (response.getStatus() != 200) {
            logger.debug("Return status for URL " + url + " is "
                    + response.getStatus());
            throw new SocialAuthException("Error while making request to URL :"
                    + url + "Status : " + response.getStatus());
        }

        return response;
    }

    @Override
    public AccessGrant getAccessGrant() {
        return accessGrant;
    }

    @Override
    public String getProviderId() {
        return config.getId();
    }

    @Override
    public void setAccessGrant(final AccessGrant accessGrant)
            throws AccessTokenExpireException, SocialAuthException {
        this.accessGrant = accessGrant;
        this.scope = accessGrant.getPermission();
        authenticationStrategy.setAccessGrant(accessGrant);

    }

    @Override
    public Response uploadImage(final String message, final String fileName,
                                final InputStream inputStream) throws Exception {
        logger.warn("WARNING: Not implemented for GitHub");
        throw new SocialAuthException(
                "Upload Image is not implemented for GitHub");
    }

    @Override
    protected List<String> getPluginsList() {
        List<String> list = Lists.newArrayList();
        if (config.getRegisteredPlugins() != null
                && config.getRegisteredPlugins().length > 0) {
            list.addAll(Arrays.asList(config.getRegisteredPlugins()));
        }
        return list;
    }

    @Override
    protected OAuthStrategyBase getOauthStrategy() {
        return authenticationStrategy;
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    private Profile doVerifyResponse(final Map<String, String> requestParams)
            throws Exception {
        logger.info("Retrieving Access Token in verify response function");
        if (requestParams.get("error_reason") != null
                && "user_denied".equals(requestParams.get("error_reason"))) {
            throw new UserDeniedPermissionException();
        }
        accessGrant = authenticationStrategy.verifyResponse(requestParams,
                MethodType.POST.toString());

        if (accessGrant != null) {
            logger.debug("Obtaining user profile");
            return getProfile();
        } else {
            throw new SocialAuthException("Access token not found");
        }
    }

    private Profile getProfile() throws Exception {
        String presp;

        try {
            Response response = authenticationStrategy.executeFeed(PROFILE_URL);
            presp = response.getResponseBodyAsString(Constants.ENCODING);
            response.close();
        } catch (Exception e) {
            throw new SocialAuthException("Error while getting profile from "
                    + PROFILE_URL, e);
        }
        try {
            logger.debug("User Profile : " + presp);
            JSONObject resp = new JSONObject(presp);
            Profile p = new Profile();
            p.setValidatedId(resp.optString("id", null));
            p.setFullName(resp.optString("name", null));
            p.setEmail(resp.optString("email", null));
            p.setLocation(resp.optString("location", null));
            p.setProfileImageURL(resp.optString("avatar_url", null));
            p.setDisplayName(resp.optString("login", null));
            p.setProviderId(getProviderId());
            if (config.isSaveRawResponse()) {
                p.setRawResponse(presp);
            }
            // 如果用户不设置公开email,上面是拿不到email的，还得继续请求获取email
            if (p.getEmail() == null || p.getEmail().isEmpty()
                    || p.getEmail().equals("null")) {
                p.setEmail(getEmail());
            }
            userProfile = p;
            return p;
        } catch (Exception ex) {
            throw new ServerDataException(
                    "Failed to parse the user profile json : " + presp, ex);
        }
    }

    /**
     * Retrieve the first valid email
     *
     * @return email
     */
    private String getEmail() throws Exception {
        Response serviceResponse;
        try {
            serviceResponse = authenticationStrategy.executeFeed(EMAILS_URL);
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to retrieve the user emails from  " + EMAILS_URL, e);
        }

        String result;
        String email = "";
        try {
            result = serviceResponse
                    .getResponseBodyAsString(Constants.ENCODING);
            logger.debug("User Emails :" + result);
        } catch (Exception e) {
            throw new SocialAuthException("Failed to read response from  "
                    + EMAILS_URL, e);
        }
        try {
            JSONArray array = new JSONArray(result);
            int length = array.length();

            for (int i = 0; i < length; i++) {
                email = array.getString(i);
                if (!email.isEmpty())
                    break;
            }
            serviceResponse.close();
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to parse the user emails json : " + result, e);
        }

        return email;
    }

    private String getScope() {
        StringBuilder result = new StringBuilder();
        String arr[];
        if (Permission.AUTHENTICATE_ONLY.equals(scope)) {
            arr = AuthPerms;
        } else if (Permission.CUSTOM.equals(scope)
                && config.getCustomPermissions() != null) {
            arr = config.getCustomPermissions().split(",");
        } else {
            arr = AllPerms;
        }
        result.append(arr[0]);
        for (int i = 1; i < arr.length; i++) {
            result.append(",").append(arr[i]);
        }
        String pluginScopes = getPluginsScope(config);
        if (pluginScopes != null) {
            result.append(",").append(pluginScopes);
        }
        return result.toString();
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
